--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════╗
--║ GRAPHICAL EFFECT FUNCTIONS ║ This contains the dedicated functions and thinkers making grind GFX feel right, like railsparks!
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════╝
local RAIL = GS_RAILS if RAIL.blockload return end

--GFX thinker, for when you need capechase, autoframing, gradual fading, offsets, or to disappear instantly when not grinding. 
RAIL.autoframe = function(s)
	--Apply autoframe behaviour? Basically an adjustable FF_ANIMATE that can loop within the same sprite.
	if s.sttspeed
		if (s.sttspeed==1)  or (leveltime % (s.sttspeed) == 0) --Animation speed. If sttspeed is 2, we'd advance frames every 2 tics.	
			--If you don't specify a frameloop, then make sure you don't exceed the amount of available frames!
			if s.sttlastframe != nil and ((s.frame & FF_FRAMEMASK) >= s.sttlastframe) 
				s.frame = ($ & ~FF_FRAMEMASK)|(s.sttfirstframe or 0)
			else
				s.frame = $+1
			end
		end
	end
	
	--Fade if sttfade exists.
	if s.sttfade and (s.fuse < 10)
		local TRANS = (s.frame & FF_TRANSMASK)
		s.frame = ($ & ~FF_TRANSMASK)|min(FF_TRANS90, (TRANS+FRACUNIT))
	end	
	
	local ANG,t = s.angle, s
	
	--Apply capechase and potentially offsets if given a tracer.
	if s.tracer and s.tracer.valid
		t = s.tracer
		local prevangle = s.angle
		A_CapeChase(s, 1, 0) if not s.valid return end --Capechase screws us sometimes, so check first!
		
		if type(s.ditchtracer)=="number" s.ditchtracer = max($-1,0)	end 
		if s.ditchtracer==0 or s.ditchtracer==true --Ditch tracer after a while?	
			s.ditchtracer,s.tracer = nil
			s.angle = prevangle --Undo capechase's angle copy.
		else
			ANG = t.angle
		end
	end	
	
	--If you set one offset, you MUST set them all, even if they're just 0.
	if s.GSoffsetX != nil
		s.GSoffsetwait = (not s.GSoffsetwait) and 2 or 1
		if s.GSoffsetang != nil ANG = s.GSoffsetang end --Probably want to make this player.drawangle if it applies.

		P_MoveOrigin(s,
		s.x+ FixedMul(s.GSoffsetX/s.GSoffsetwait, cos(ANG)),
		s.y+ FixedMul(s.GSoffsetY/s.GSoffsetwait, sin(ANG)),
		s.z+ (s.GSoffsetZ*P_MobjFlip(t)) )

		if s.GScopymom and s.GScopymom != 100  --Adjust XY offset distance overtime. Lower numbers means more distance, higher means LESS distance.
			s.GSoffsetX = FixedDiv(FixedMul($, 100), s.GScopymom)			
			s.GSoffsetY = FixedDiv(FixedMul($, 100), s.GScopymom)
			s.GScopymom = min($*30/29, 100) //FixedDiv(FixedMul($, 100), s.GScopymom)		
		end
	end
	
	--Be grinding or die!
	if s.GSgrinder
		local t = s.GSgrinder 
		if not t.valid or not t.health or not (t.GSgrind and t.GSgrind.grinding) or t.GSgrind.turntoia==10
			if s.GSgrinderExtra=="tracer"
				s.tracer,s.target,s.ditchtracer = nil --Eh, just ditch tracer then.
				s.tics = 11 if s.fuse s.fuse = min($, 10) end
			else
				P_RemoveMobj(s) return --
			end
		end
	end
end

--Sonic Adventure-style spintrail, edited from SA-Sonic to work for Grind Rails. Can also be called with GS.speedtrail.
--s and GS are mandatory, but otherwise all arguments are optional.
RAIL.SpeedTrail = function(s, GS, BLEND, COLOR, FUSE, SCALESPEED)
	if not (GS) or (GS.alreadytrailed) or (type(GS.LastXYZ)!="table") return end --
	local RENDERER,FIRSTPERSON,FRAME,DENSITY,SPRITE = 1, false, 44|FF_TRANS50, 16*s.scale, SPR_GSR2
	COLOR = (COLOR) or s.color
	BLEND = (BLEND) or AST_ADD
	
	if s.player
		local p = s.player
		if p==displayplayer
			FIRSTPERSON = (not (CV_FindVar("chasecam").value))
			RENDERER = CV_FindVar("renderer").value --If it's 2, we're in OpenGL. 1 is software. 0 is dedicated server/error.
			if RENDERER==2 --2 is OpenGL and needs more translucency.
				FRAME = $+3<<16
				DENSITY = 10*s.scale	
			end	
		end
		local SKIN = GS_RAILS_SKINS[s.skin]
		if SKIN
			local STATS = SKIN["S_SKIN"]
			if STATS
				if STATS.SPEEDTRAIL=="never"
					return --
				elseif STATS.SPEEDTRAIL=="none"
					local rail = GS.myrail
					local CONTINUE = false
					if rail and rail.GSspeedtrail and GS.grinding and GS.railspeed!=nil
						local CONVEYOR = 0 if (rail.GSconveyor) and rail.GSconveyor<<16 end
						if (abs(GS.railspeed+CONVEYOR) > rail.GSspeedtrail*s.scale)
							CONTINUE = true
						end
					end
					if CONTINUE==false return end --Nope, only continue if it's a rail gimmick.
				end
				if STATS.SPEEDTRAILCOLOR and type(STATS.SPEEDTRAILCOLOR)=="number"
					COLOR = STATS.SPEEDTRAILCOLOR
				end
			end
			if SKIN["SpeedTrailVariables"]
				local BLEND2,COLOR2,FUSE2,SCALESPEED2,DENSITY2,FRAME2,SPRITENEW = SKIN["SpeedTrailVariables"](p, s, GS)
				if BLEND2!=nil
					if BLEND2==true return end --If this returns true, the speedtrail won't be played at all!
					BLEND = BLEND2
				end
				if COLOR2!=nil COLOR = COLOR2 end --Use any custom-specified values over the originals, unless it's nil.
				if FUSE2!=nil FUSE = FUSE2 end
				if SCALESPEED2!=nil SCALESPEED = SCALESPEED2 end
				if DENSITY2!=nil DENSITY = DENSITY2 end
				if FRAME2!=nil FRAME = FRAME2 end
				if SPRITENEW!=nil SPRITE = SPRITENEW end
			end
		end
	end
	if GS.forcetrailcolor
		COLOR = GS.forcetrailcolor
		GS.forcetrailcolor = nil
	end
	GS.alreadytrailed = true
	
	local LX,LY,LZ = GS.LastXYZ.X, GS.LastXYZ.Y, GS.LastXYZ.Z
	local MOMX,MOMY,MOMZ = s.x-LX, s.y-LY, s.z-LZ

	local DIST = FixedDiv(FixedMul((R_PointToDist2(0,0,R_PointToDist2(0,0,MOMX,MOMY),MOMZ)/DENSITY), FRACUNIT), s.scale)
	DIST = max(1, min(99,$)) --Why would we EVER need more than 100 per tic?
	local XDIST,YDIST,ZDIST = MOMX/DIST, MOMY/DIST, MOMZ/DIST
	local SCALE,ST = s.scale*10/9
	
	--Keep counting down by -1, spawning sprites until we reach 0.
	for i = DIST,1,-1	
		ST = P_SpawnMobjFromMobj(s, (XDIST*i)-MOMX, (YDIST*i)-MOMY, (ZDIST*i)-MOMZ, MT_THOK)
		ST.flags = $|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_NOGRAVITY --Just incase.
		ST.scale = SCALE
		ST.blendmode = BLEND
		ST.sprite = SPRITE
		ST.frame = 44|FRAME
		ST.color = COLOR
		if FUSE 
			ST.tics,ST.fuse = FUSE,FUSE
		end
		if SCALESPEED
			ST.scalespeed = SCALESPEED
			ST.destscale = 1
			P_SetObjectMomZ(ST, SCALESPEED*11, true)
		end
		if (i < 3) and FIRSTPERSON
			ST.flags2 = $|MF2_DONTDRAW
		end
	end
end

--Special spark when landing on rail.
RAIL.LandingSpark = function(s, COLOR, COLORIZED, HANGRAIL, SCALE)
	local ghs = P_SpawnMobjFromMobj(s,0,0,7, MT_GSRAILGFX) 
	ghs.sprite = SPR_GSR2 
	ghs.frame = FF_TRANS20|FF_FULLBRIGHT|((s.frame&FF_VERTICALFLIP) and FF_VERTICALFLIP or 0)
	ghs.blendmode = AST_ADD
	ghs.sttfirstframe,ghs.sttlastframe = A, C
	ghs.sttfade = 16
	ghs.dispoffset = 44
	ghs.spriteyoffset = ((HANGRAIL) and 16 or -9)<<16
	if COLOR
		ghs.color = COLOR
		ghs.colorized = (COLORIZED) and true or false
	elseif HANGRAIL
		ghs.color,ghs.colorized = SKINCOLOR_GREY,true
	end
	ghs.fuse,ghs.tics = 16,16
	if SCALE
		ghs.scale = FixedDiv(FixedMul(SCALE, s.scale), FRACUNIT)
	else
		ghs.scale = s.scale/2
	end
	ghs.destscale = 99<<16
	ghs.sttspeed = 1
	ghs.GSgrinder,ghs.GSgrinderExtra = s,"tracer"
	ghs.tracer,ghs.ditchtracer = s,14
	return ghs --
end

--Spawns speedlines when grinding fast.
RAIL.SpeedLines = function(s, GS, ANG, ANIM)
	if GS==nil or s==nil return false end --RAIL.error("Warning! s or GS not given in RAIL.speedlines!") 
	local p = s.player
	if ANG == nil ANG = p and p.drawangle or s.angle end
	local TIME = GS.grinding or leveltime or 1
	local TMOMX,TMOMY = -FixedMul(abs(GS.railspeed or 0), cos(ANG)), -FixedMul(abs(GS.railspeed or 0), sin(ANG))
	
	local RNG = (TIME%8==0) and 33 or (TIME%4==0) and 9 or (TIME%3==0) and 21 or (TIME%2==0) and 16 or 27
	local RNGZ = (TIME%9==0) and 16 or (TIME%5==0) and 57 or (TIME%4==0) and 24 or (TIME%2) and 47 or 32
	local ANGLE = ANG+ANGLE_90
	local YOFFSET,ghs = (s.eflags & MFE_VERTICALFLIP) and -46<<16 or 1
	local SCALE = s.scale if SCALE==90000 SCALE = FRACUNIT end
	local HANG = (RAIL.GrindState(s, "hang")) and -s.height*2/3 or 0
	
	local FOCUSGRIND = nil
	local TMOMZ = -s.momz
	if GS.grinding
		TMOMZ = s.z-GS.LastGrindZ
	end
	if s.height
		if ANIM=="leap"
			SCALE = $*4/3
		end
		local DIV = 36
		if p
			DIV = (p.height) and p.height/FRACUNIT or 36
			if GS.crouchgrind and GS.crouchbutton and ANIM!="leap"
				FOCUSGRIND = (leveltime%3==0) and SKINCOLOR_GOLDENROD or (leveltime%2==0) and SKINCOLOR_KETCHUP or SKINCOLOR_FLAME
			end
		end
		SCALE = FixedDiv(FixedMul($, s.height/DIV), FRACUNIT)
	end
	local THRUST = ((TMOMZ*P_MobjFlip(s))<-4*s.scale) and 1*s.scale or 3*s.scale
	
	for i = -1,1,2
		ghs = P_SpawnMobjFromMobj(s,
		-TMOMX/4 + FixedMul(RNG*SCALE, cos(ANGLE)),
		-TMOMY/4 + FixedMul(RNG*SCALE, sin(ANGLE)), 
		TMOMZ + (RNGZ*SCALE) + HANG, MT_GSRAILGFX)
		
		ghs.flags = $|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY|MF_NOBLOCKMAP
		ghs.momx,ghs.momy,ghs.momz = TMOMX/3,TMOMY/3,TMOMZ/3	
		ghs.spriteyoffset = s.spriteyoffset+YOFFSET
		
		if (s.eflags & MFE_UNDERWATER)
			ghs.fuse = 7
			ghs.state = (TIME%2) and S_SMALLBUBBLE or S_MEDIUMBUBBLE
		else
			ghs.sprite = SPR_GSR2
			ghs.renderflags = $|RF_PAPERSPRITE
			if ANIM == "leap"
				ghs.frame = 41|FF_PAPERSPRITE|FF_TRANS20
				ghs.blendmode = AST_ADD
				ghs.renderflags = $|RF_SEMIBRIGHT
				ghs.color = (FOCUSGRIND) and FOCUSGRIND or s.color or SKINCOLOR_GREY
				ghs.angle = ANG+(ANG1/3)*RNG*i
				P_Thrust(ghs, ANG, -8*s.scale)
				if (TIME%3==0)
					ghs.spritexscale,ghs.spriteyscale = $*3/4,$*3/4
				elseif (TIME%2==0)
					ghs.spritexscale,ghs.spriteyscale = $*4/3,$*3/4
				else
					ghs.spritexscale,ghs.spriteyscale = $/2,$/2				
				end
			else
				ghs.frame = 40|FF_PAPERSPRITE|FF_TRANS20
				ghs.angle = ANG+ (ANG1/9)*RNG*i		
			end
			if p
				if (FOCUSGRIND) 
					ghs.blendmode = AST_ADD
					ghs.color,ghs.colorized = FOCUSGRIND, true						
					if (leveltime%7==0 or leveltime%6==0)	
						ghs.frame = 41|FF_TRANS40|FF_FULLBRIGHT|FF_PAPERSPRITE
						ghs.spritexscale,ghs.spriteyscale = $*5/6,$*5/6
					else
						ghs.frame = 40|FF_TRANS10|FF_PAPERSPRITE
						ghs.spritexscale,ghs.spriteyscale = $+11500,$+3500
					end
				elseif (p.powers[pw_super] or p.hypermysticsonic)
					ghs.renderflags = $|RF_FULLBRIGHT
					ghs.blendmode = AST_ADD
					ghs.color,ghs.colorized = s.color or SKINCOLOR_WHITE,true					
				end
			end
			ghs.sttfade = true
			P_Thrust(ghs, ghs.angle, -THRUST)
			ghs.fuse,ghs.tics = 11,33
		end
		ANGLE = $+ANGLE_180
	 end
end


--For aiding in rail sidehops. They only appear when tutorials are on.
RAIL.SpawnArrow = function(s, ANGLE, COLOR, FUSE, RL, DOTWO)
	local ghs = s.GSarrow
	local NEW = false
	if not (ghs and ghs.valid) or (ghs.RL and ghs.RL!=RL)
		s.GSarrow = P_SpawnMobjFromMobj(s, 0,0,1, MT_GSRAILGFX)
		ghs = s.GSarrow
		ghs.scale = s.scale/4
		ghs.scalespeed = s.scale/16
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS|RF_PAPERSPRITE
		ghs.dispoffset = -47
		ghs.spriteyoffset = 15<<16
		ghs.sprite = SPR_GSR2
		ghs.init = 5
		ghs.RL = RL
		ghs.sttfade = 10
		NEW = true
	end
	local DIST, TRANS = 80*s.scale, FF_TRANS30
	if (ghs.init < 37)  --Extend out gracefully.
		TRANS = min(FF_TRANS90, max(3, 10-(ghs.init/3))<<16)
		DIST = min(80*s.scale, ghs.init*3*s.scale)
		ghs.spriteyoffset = max(15, 75-(ghs.init*3))<<16
	end
	P_MoveOrigin(ghs, s.x-FixedMul(DIST, cos(ANGLE)), s.y-FixedMul(DIST, sin(ANGLE)), s.z+1)
	ghs.eflags = ($ & ~MFE_VERTICALFLIP)|(s.eflags & MFE_VERTICALFLIP)
	ghs.frame = 42|TRANS|FF_PAPERSPRITE
	ghs.angle = ANGLE or 0
	ghs.destscale = s.scale
	ghs.color = COLOR or SKINCOLOR_GOLDENROD 
	ghs.fuse = FUSE or 6
	ghs.init = $+1 
	if (DOTWO) and ghs
		local TWO = s.GSarrow.arrow
		if not (TWO and TWO.valid)
			s.GSarrow.arrow = P_SpawnMobjFromMobj(s, 0,0,1, MT_GSRAILGFX)
			TWO = s.GSarrow.arrow
		end
		TWO.spriteyoffset = ghs.spriteyoffset
		TWO.scalespeed = ghs.scalespeed
		TWO.renderflags = ghs.renderflags
		TWO.dispoffset = ghs.dispoffset
		TWO.sprite = ghs.sprite
		TWO.RL = ghs.RL
		TWO.sttfade = ghs.sttfade
			
		P_MoveOrigin(TWO, s.x+FixedMul(DIST, cos(ANGLE)), s.y+FixedMul(DIST, sin(ANGLE)), s.z+1)
		TWO.eflags = ghs.eflags
		TWO.frame = ghs.frame --Add HFLIP, simple!
		TWO.angle = ghs.angle+ANGLE_180
		TWO.scale = ghs.scale
		TWO.color = ghs.color
		TWO.fuse = ghs.fuse
		TWO.init = ghs.init
		end
	return ghs --
end

--Simple dust when failing to grind very well.
RAIL.FailDust = function(s, COLOR, FUSE, SCALE)
	SCALE = SCALE or s.scale
	local ANGLE = s.angle if s.player ANGLE = s.player.drawangle end
	local SIDE = (leveltime%2) and ANGLE_90 or -ANGLE_90
	local ghs = P_SpawnMobjFromMobj(s, FixedMul(12*SCALE, cos(ANGLE+SIDE)), FixedMul(12*SCALE, sin(ANGLE+SIDE)), SCALE, MT_GSRAILGFX)
	ghs.renderflags = $|RF_SEMIBRIGHT
	ghs.scale = SCALE
	ghs.scalespeed = SCALE/15
	ghs.destscale = SCALE*2
	ghs.dispoffset = 90
	
	local NUM = ((leveltime%5==0) and 2 or (leveltime%3==0) and 6 or 4)
	ghs.momz = NUM*s.scale*P_MobjFlip(s)
	ghs.momx = FixedMul(NUM*SCALE, cos(ANGLE+SIDE))
	ghs.momy = FixedMul(NUM*SCALE, sin(ANGLE+SIDE))
	if (s.eflags & MFE_UNDERWATER)
		ghs.state = S_SPINDUST_BUBBLE1
	else
		ghs.state = S_SPINDUST1
		ghs.blendmode = AST_ADD
		ghs.flags2 = $|MF2_SHADOW
		if COLOR
			ghs.color,ghs.colorized = COLOR, true
		end
	end
	if FUSE
		ghs.fuse = FUSE
		ghs.sttfade = max(1, FUSE-4)
	end	
	return ghs --
end

--Special wind originating from windrail segments themselves. Specifically, their sides.
RAIL.RailWind = function(s, i, COLOR, SCALE, YOFFSET, SEGSPAWN)
	local ABANG = abs(s.rollangle) if (ABANG > ANGLE_45) return end --Nevermind, too steep for this.
	if not (i) i = (leveltime%2) and 1 or -1 end
	local TIME = leveltime
	local ANG = s.angle+ANGLE_180 //RAIL.ANG(s.GSmainrail or s)+ANGLE_180
	local FRAME,MOMZ = 33,0 --7
--	local YMULT = 0
	local ZDIST = (TIME%19==0) and -56 or (TIME%11==0) and -38 or (TIME%7==0) and -16 or (TIME%4==0) and 30 or 10
--	local DOROLL = 0
	if (ABANG > ANG2) 
	--	if (ABANG >= ANG1*5) or (SEGSPAWN)
	--		FRAME = 34
	--		DOROLL = -s.rollangle
	--	else
			if (s.rollangle > 0) and (ANG < 0)
				FRAME = 38|FF_HORIZONTALFLIP
			elseif (s.rollangle < 0) and (ANG >= 0)
				FRAME = 38 --c
			end
			MOMZ = (s.rollangle/(ANG1/12))<<12
			ZDIST = $+20+abs(s.rollangle/(ANG1*4/5))
			if (MOMZ < 0)
				ZDIST = $- min(39, abs(MOMZ/21000)+8)
			else
				MOMZ = $*2/3
			end
		/*	if (ABANG < ANG1*4)
				YMULT = -(ABANG/ANG1)
			elseif (ABANG > ANG2*4) --Steep slope. Time to overcurve a bit.
				YMULT = (ABANG/ANG1)-8
			end */
	--	end
	else
		FRAME = 34 --8 --straight-laced wind.
	end
	local DIST = (TIME%4==0) and 9 or (TIME%3==0) and 24 or (TIME%2==0) and 33 or 41
	local ghs = P_SpawnMobjFromMobj(s, FixedMul((DIST*i)<<16, cos(ANG+ANGLE_90)), FixedMul((DIST*i)<<16, sin(ANG+ANGLE_90)), ZDIST<<16, MT_GSRAILGFX)
	if ghs and ghs.valid
		ghs.renderflags = $|RF_SEMIBRIGHT|RF_PAPERSPRITE
		ghs.blendmode = AST_ADD
		local THRUST = ((TIME%5==0) and 12 or (TIME%4==0) and 16 or (TIME%3==0) and 21 or (TIME%2==0) and 30 or 37)<<16
		if SEGSPAWN
			P_Thrust(ghs, ANG, THRUST/2)
			ghs.fuse,ghs.tics = (TIME%2==0) and 9 or 11, 22
		else
			P_Thrust(ghs, ANG, THRUST)		
			ghs.fuse,ghs.tics = (TIME%2==0) and 13 or 16, 22
		end
		ghs.sttfade = (TIME%2!=0) and 18 or true
		ghs.angle = ANG-( ((TIME%5==0) and -ANG2 or ANG2)*i)  --Slight angle for visibility.	
		ghs.sprite = SPR_GSR2 
		ghs.frame = FRAME|((TIME%3==0) and FF_TRANS30 or (TIME%2!=0) and FF_TRANS40 or FF_TRANS20)
		ghs.sttfade = (TIME%2!=0) and 18 or true
		ghs.spriteyoffset = YOFFSET or (TIME%5==0) and -1<<16 or (TIME%3==0) and -7<<16 or -13<<16
		ghs.color = COLOR or SKINCOLOR_GREY
		ghs.scale = (SCALE or $)*14 / ((TIME%4==0) and 12 or (TIME%3==0) and 10 or (TIME%2==0) and 13 or 8)
		ghs.spritexscale = ($*7)/((TIME%10==0) and 6 or (TIME%9==0) and 4 or (TIME%5==0) and 9 or 4) 
		ghs.spriteyscale = ($*7)/((TIME%11==0) and 5 or (TIME%7==0) and 9 or (TIME%6==0) and 11 or 7)
		
		if FRAME==34 --is it 8? It's a straight wind line.
		--	if DOROLL
		--		ghs.spritexscale,ghs.spriteyscale = $*2,$*2
		--		ghs.rollangle = DOROLL
		--	else
				ghs.sttspeed = 2
				ghs.sttfirstframe = Z+8
				ghs.sttlastframe = Z+11		
		--	end
		else
			ghs.momz = $-MOMZ //(SEGSPAWN==true) and $-(MOMZ/2) or $-MOMZ
			ghs.frame = $+FF_TRANS10
			if (ABANG > ANG1*7)
				if (ghs.frame & FF_HORIZONTALFLIP)
					ghs.rollangle = (s.rollangle/2)+(s.rollangle/4)
				else
					ghs.rollangle = -(s.rollangle/2)-(s.rollangle/4)
				end
				ghs.frame = $+FF_TRANS10
			end
		/*	if YMULT
				ghs.spriteyscale = FixedDiv(FixedMul($, 24+YMULT), 24)
				if (YMULT > 0)
					ghs.spritexscale = FixedDiv(FixedMul($, 48), 48+YMULT)
				end
			end */
		end
		return ghs --
	end
end

--Super basic leaves function.
RAIL.SpawnLeaves = function(s, COLOR, SCALE, ANG, RNG, HANGRAIL)
	if ANG == nil ANG = s.angle+(ANG60*((leveltime%2) and -1 or 1)) end
	RNG = RNG or 0
	local DIST = (not s.player) and RNG or (32*s.scale)-max(32*s.scale, RNG)
	local ghs = P_SpawnMobjFromMobj(s,FixedHypot(DIST, cos(ANG)),FixedHypot(DIST, sin(ANG)),((HANGRAIL) and s.height or 0)+DIST/8, MT_GSRAILGFX)
	ghs.sprite = SPR_GSR2
	ghs.frame = (Z+7)|((leveltime%2) and FF_HORIZONTALFLIP or 0)|((leveltime%3==0) and FF_VERTICALFLIP or 0)
	ghs.sttfade = true
	ghs.flags = $ & ~MF_NOGRAVITY
	ghs.fuse,ghs.tics = 44,99
	if COLOR
		ghs.color,ghs.colorized = COLOR, true
	end
	P_SetObjectMomZ(ghs, (((HANGRAIL) and -5 or 5)<<16)+abs(DIST/4), true)
	P_Thrust(ghs, ANG, abs(RNG/4))
	ghs.scale = SCALE and FixedDiv(FixedMul(SCALE, s.scale), FRACUNIT)/2 or s.scale/2
	ghs.spriteyscale = (leveltime%7==0) and $*5 or (leveltime%6==0) and $*4 or (leveltime%5==0) and $*3 or $*2
	ghs.spritexscale = ghs.spriteyscale	
	if (leveltime%3==0)
		ghs.renderflags = $|RF_PAPERSPRITE
		ghs.angle = ANG+(RNG*90)
		ghs.spritexscale,ghs.spriteyscale = $*2,$*2
	end
	return ghs --
end

--Super basic flamedust function. Spawns fire particles.
RAIL.SpawnFlameDust = function(s, COLOR, SCALE, HANGRAIL)
	local ANG = s.angle+ANGLE_90
	if s.player
		ANG = s.player.drawangle+ANGLE_90
	end
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, s.scale, MT_GSRAILGFX)
	ghs.sprite = SPR_GSR2
	ghs.frame = (Z+4)|FF_FULLBRIGHT|FF_TRANS10
	ghs.renderflags = $|RF_FULLBRIGHT|RF_NOCOLORMAPS
	ghs.blendmode = AST_ADD
	ghs.destscale = 99<<16
	ghs.sttfade = true
	ghs.fuse,ghs.tics = 11,99
	if COLOR
		ghs.color,ghs.colorized = COLOR, true
	end
	ghs.scale = SCALE and FixedDiv(FixedMul(SCALE, s.scale*2), FRACUNIT) or s.scale*2/3
	ghs.spriteyscale = (leveltime%3==0) and $/2 or (leveltime%2==0) and $*2/3 or $
	ghs.spritexscale = (leveltime%2) and ghs.spriteyscale or ghs.spriteyscale*3/2
	if (HANGRAIL) or s.state==S_PLAY_RIDE
		ghs.spriteyoffset = $+38<<16
		P_SetObjectMomZ(ghs, -((leveltime%2) and 3 or 1)<<16, true)	
	else
		P_SetObjectMomZ(ghs, ((leveltime%2) and 5 or 3)<<16, true)
	end
	return ghs --
end

--Super basic bubbledust function. Spawns tiny purely cosmetic bubbles.
RAIL.SpawnBubbleDust = function(s, COLOR, SCALE, HANGRAIL)
	local ANG = s.angle if s.player ANG = s.player.drawangle end
	ANG = (leveltime%2) and $+ANGLE_90 or $-ANGLE_90
	local ghs = P_SpawnMobjFromMobj(s, FixedMul(((leveltime%3==0) and 10 or 16)*s.scale, cos(ANG)),
	FixedMul(((leveltime%3==0) and 10 or 16)*s.scale, sin(ANG)), 1, MT_GSRAILGFX)
	ghs.sprite = SPR_BUBL
	ghs.frame = A|((leveltime%2) and FF_HORIZONTALFLIP or 0)
	ghs.flags = $|MF_BOUNCE|MF_GRENADEBOUNCE|MF_NOCLIPTHING|MF_NOBLOCKMAP & ~MF_NOGRAVITY
	ghs.renderflags = $|RF_SEMIBRIGHT
	ghs.destscale = 99<<16
	ghs.sttfade = true
	ghs.fuse,ghs.tics = 28,99
	if COLOR
		ghs.color,ghs.colorized = COLOR, true
	end
	ghs.scale = (SCALE and SCALE or s.scale)*((leveltime%5==0) and 3 or 2)
	ghs.spriteyscale = (leveltime%3==0) and $/2 or (leveltime%2==0) and $*2/3 or $
	ghs.spritexscale = (leveltime%2) and ghs.spriteyscale or ghs.spriteyscale*3/2
	P_Thrust(ghs, ANG, (leveltime%3==0) and 5*s.scale or 8*s.scale)
	if (HANGRAIL) or s.state==S_PLAY_RIDE
		ghs.spriteyoffset = $+38<<16
		P_SetObjectMomZ(ghs, -((leveltime%2) and -1 or -4)<<16, true)	
	else
		P_SetObjectMomZ(ghs, (leveltime%2) and -3<<16 or 1<<15, true)
	end
	return ghs --
end

--Super basic dust function.
RAIL.SpawnDust = function(s, COLOR, SCALE, RNG, HANGRAIL)
	if not RNG RNG = P_RandomRange(-16,16) or 1 end						
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, ((HANGRAIL) and s.height or 0)+(s.height/4), MT_SPINDUST)
	ghs.state = $+abs(RNG/5)
	if COLOR
		ghs.color,ghs.colorized = COLOR, true
	end
	ghs.scale = SCALE and FixedDiv(FixedMul(SCALE, s.scale*2), FRACUNIT) or s.scale*2/3
	ghs.scalespeed = s.scale/8
	ghs.destscale = 99<<16
	P_SetObjectMomZ(ghs, abs( RNG/((HANGRAIL) and -2 or 2) ) <<16)
	P_Thrust(ghs, (s.player and s.player.drawangle or s.angle)+((leveltime%2) and -ANG20 or ANG20), max(1, abs(RNG))*s.scale)	
	return ghs --
end

--Newer sparks that do not appear on hangrails.
RAIL.NewSparks = function(s, rail, COLOR, COLORIZED, SCALE, STATS)
	local GS = s.GSgrind if GS==nil return end
	if GS.railsparks==nil GS.railsparks = {} end
	local SPARKS = GS.railsparks
	
	local FOCUSGRIND = nil
	if GS.crouchgrind and GS.crouchbutton and not GS.cantcrouch and not (STATS and STATS.CROUCH==false)
		if ((GS.railfalltimer or 0) > 7)
			FOCUSGRIND = SKINCOLOR_GREY
		else	
			FOCUSGRIND = (leveltime%3==0) and SKINCOLOR_KETCHUP or (leveltime%2==0) and SKINCOLOR_GARNET or SKINCOLOR_FLAME
		end
	end
	
	for i = -1,1,2
		local ghs = GS.railsparks[i]
	/*	if ghs and ghs.valid and ((ghs.frame & FF_FRAMEMASK) >= ((ghs.sttlastframe or 0)-1))
			ghs.fuse,ghs.sttfade = 9,true
			ghs = nil
 		end */
		if not (ghs and ghs.valid)	
			ghs = P_SpawnMobjFromMobj(s,0,0,7,MT_GSRAILGFX) 
			ghs.renderflags = $|RF_FULLBRIGHT|RF_PAPERSPRITE
			ghs.blendmode = AST_ADD
			ghs.sprite = SPR_GSR2
			ghs.frame = E|FF_FULLBRIGHT|FF_TRANS30|FF_PAPERSPRITE
			ghs.sttfirstframe = E
			ghs.sttlastframe = S
			ghs.sttspeed = 1
			ghs.sttfade = 14
			ghs.dispoffset = 111
			ghs.GSgrinder,ghs.GSgrinderExtra = s,"tracer" --For fading later.
			GS.railsparks[i] = ghs
			ghs.color = (FOCUSGRIND) and FOCUSGRIND or (COLOR and type(COLOR)=="number") and COLOR or SKINCOLOR_GRINDSPARK
			local FIRSTSCALE = (SCALE) and FixedDiv(FixedMul(SCALE, s.scale), FRACUNIT) or s.scale/2
			ghs.scale = FIRSTSCALE/2
			ghs.destscale = FIRSTSCALE
		else
			ghs.renderflags = $|RF_PAPERSPRITE
			ghs.spritexscale,ghs.spriteyscale = 63536,63536
			if ((ghs.frame & FF_FRAMEMASK) >= (ghs.sttlastframe-2)) 
				local RNG = P_RandomRange(0,8)
				ghs.spritexoffset = (4-RNG)<<16
				ghs.spriteyoffset = (-5+RNG)<<16
			--	ghs.frame = ($ & ~FF_FRAMEMASK) --If still active, loop a bit early!
				ghs.scale = (SCALE) and FixedDiv(FixedMul(SCALE, s.scale), FRACUNIT) or s.scale/2
			end
		end
		ghs.fuse,ghs.tics = 24,24
		ghs.color = (FOCUSGRIND) and FOCUSGRIND or (COLOR and type(COLOR)=="number") and COLOR or SKINCOLOR_GRINDSPARK
		
		if userdataType(rail)=="mobj_t" and GS and GS.raildirection!=nil and rail.valid and GS.railspeed --Face the right angle!
			if GS.MyRail and GS.MyRailStats.VERTRAIL --Vertical rail? Use the stats, not the rail property.
				if not GS.MyRailStats.ZSPEED ghs.flags2,ghs.fuse = $|MF2_DONTDRAW, 2 break end				
				ghs.GSgrinderExtra = nil --For fading later.
				ghs.dispoffset = -19
				ghs.spritexoffset,ghs.spriteyoffset = 0,0		
				ghs.renderflags = $ & ~RF_PAPERSPRITE
				ghs.scale = s.scale*3/4 --Fixed scale here.
				
				local ZSPEED = GS.MyRailStats.ZSPEED or 0
				ghs.dispoffset = -19
				ghs.frame = ($&~FF_HORIZONTALFLIP)|((GS.railspeed < 0) and FF_HORIZONTALFLIP or 0)
				ghs.rollangle = (ZSPEED < 0) and ANGLE_270 or ANGLE_90
				P_MoveOrigin(ghs, s.x, s.y, s.z-min(0, ZSPEED))
				break --One's enough.
			else
				local RAILANG = RAIL.ANG(rail)
				local ANG = RAILANG+GS.raildirection
				ghs.angle = ANG+(ANG2*((leveltime%2) and 12 or 11)*i)+ANGLE_180
				local DIST = max(32*min(9<<16, max(s.scale, ghs.scale)), s.radius)
				local XDIST = FixedMul(GS.railspeed, cos(RAILANG)) + FixedMul(DIST, cos(ANG+(ANGLE_90*i))) + FixedMul(DIST*5/2, cos(ANG))
				local YDIST = FixedMul(GS.railspeed, sin(RAILANG)) + FixedMul(DIST, sin(ANG+(ANGLE_90*i))) + FixedMul(DIST*5/2, sin(ANG))
				ghs.rollangle = 0
				if FOCUSGRIND
					if FOCUSGRIND==SKINCOLOR_GREY
						ghs.frame = ($&~FF_TRANSMASK)|FF_TRANS40
						ghs.spritexscale,ghs.spriteyscale = $*3/4,$*3/4					
					else
						ghs.frame = ($&~FF_TRANSMASK)|FF_TRANS10
						local ADD = (leveltime%2) and 8850 or 1950
						ghs.spritexscale,ghs.spriteyscale = $+ADD, $+(ADD/3)
					end
				else
					ghs.frame = ($&~FF_TRANSMASK)|FF_TRANS30
				end
				P_MoveOrigin(ghs, s.x-XDIST, s.y-YDIST, s.z+1)
			end
		else
			ghs.angle = s.angle break --
		end
	end
	return SPARKS --
end

local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end

--Make sparks fly! Rng and rng don't have to be random, setting rng to 20 and rng2 to 0 creates consistent static sparks.
--There are several type of sparks. Generic (Rolling/Enemies), elaborate(Grinding sprites), and hangsliding (Dust, pretty much)
--Btw, don't call this function if you're not on a rail.
RAIL.RailSparks = function(s, sparknum, rng, rng2, hangrail)
	local GS = s.GSgrind
	if GS==nil or (GS.grinding%2==0) and not ((abs(GS.railspeed) > 20*s.scale) or (GS.grinding < 7))
		return --No spamming sparks unless moving really fast!
	end
	local p = s.player --Not necessarily valid!
	local RENDERER = 1 --As usual, Software can't handle things, so turn it down for old grandpa.
	if p and (p==displayplayer)
		RENDERER = CV_FindVar("renderer").value
	end
	local Zoffset = 0 --Hanging offset?
	local ang = RAIL.ANG(GS.myrail) --Rail angle?
	local Rspeed = abs(GS.railspeed) or 1 --Convenient always positive version of GSrailspeed that won't bitch if I divide by 0.
	if (GS.railspeed>=0) ang = $+ANGLE_180 end

	--Smoke dust when hanging on rails.
	if hangrail 
		local ExtraRNG = P_RandomRange(-16,16) or 1
		Zoffset = s.height*4/5
		if type(hangrail)=="number"
			Zoffset = tonumber(hangrail)
		end
		
		local ghs = P_SpawnMobjFromMobj(s, 
		ExtraRNG<<16, 
		ExtraRNG<<16, 
		Zoffset + (ExtraRNG*(FRACUNIT)) + 10*s.scale, MT_SPINDUST)
		
		ghs.scale = $/max(2, abs(ExtraRNG/3))
		ghs.state = $+abs(ExtraRNG/4)
		P_SetObjectMomZ(ghs, -abs(ExtraRNG/4)<<16)
		P_InstaThrust(ghs, ang, Rspeed/max(1,abs(ExtraRNG/3))   )
		ghs.destscale = 99<<16
		if RENDERER==1 and (leveltime%2==0)
			ghs.flags2 = $|MF2_DONTDRAW
		end

	else --Flaming speed lines! Technically from Sonic & The Secret Rings rather than Adventure, but it looks cool!
	
		if (GS.grinding%2==0) and (Rspeed > 40*s.scale) and P_IsValidSprite2(s, SPR2_GRND)
			local spk
			local FlashMobjC={SKINCOLOR_SUPERGOLD5, SKINCOLOR_RED ,SKINCOLOR_FLAME, SKINCOLOR_SUNSET}
			for d = -1,1,2
				local ExtraRNG = P_RandomRange(-25,25) or 1
				local ExtraRNG2 = P_RandomRange(-25,25) or 1
				spk = P_SpawnMobjFromMobj(s, 
				FixedMul(abs(ExtraRNG*3/4)*s.scale, cos(ang+(ANGLE_90*d)) ),
				FixedMul(abs(ExtraRNG*3/4)*s.scale, sin(ang+(ANGLE_90*d)) ),
				Zoffset+abs(ExtraRNG2*s.scale)+(s.scale*5), MT_GSRAILGFX)
				spk.renderflags = $|RF_FULLBRIGHT|RF_PAPERSPRITE
				spk.blendmode = AST_ADD
				spk.sprite = SPR_GSR2
				spk.frame = D|FF_TRANS10	
				spk.color = FlashMobjC[P_RandomRange(1,4)]
				spk.tics,spk.fuse = 10,9
				spk.spriteyscale = FRACUNIT/3
				spk.scale = $*abs(ExtraRNG2/3)/8
				spk.angle = ang-(max(4, abs(ExtraRNG))*(ANG1/3*d))
			--	spk.sttfade = true
				spk.GSgrinder = s
				if GS.MyRailStats and GS.MyRailStats.VERTRAIL
					local ZSPEED = GS.MyRailStats.ZSPEED or 0	
					spk.spritexscale = $/2
					spk.renderflags = $ & ~RF_PAPERSPRITE
					if (ZSPEED < 0)
						spk.rollangle = ANGLE_270
						P_MoveOrigin(spk, s.x, s.y, s.z+max(abs(ZSPEED), abs(s.height)))
					else
						spk.rollangle = ANGLE_90
						P_MoveOrigin(spk, s.x, s.y, s.z+min(-11*s.scale, -abs(ZSPEED)))
					end
					spk.momz = -ZSPEED
				else
					spk.rollangle = -abs(ExtraRNG2)*ANG1
					spk.momz = $+(s.z-GS.LastGrindZ)+(abs(ExtraRNG/5)*s.scale)
				end
				if RENDERER==1
					spk.flags2 = $|MF2_DONTDRAW --Save processing for Software.
				end				
			end
		end
	end
	
	--Minecart style sparks! For attaching to or rolling on rails.
	if (GS.grinding < ((hangrail) and 4 or 7)) or ((s.sprite2&~SS2)==SPR2_ROLL) and (GS.grinding%3==0)
		local ExtraRNG,mpk
		for d = 1,sparknum
			ExtraRNG = P_RandomRange(-20,20)*s.scale
			mpk = P_SpawnMobjFromMobj(s, ExtraRNG, ExtraRNG, Zoffset, (d%2) and MT_THOK or MT_MINECARTSPARK)
			if (d%2)
				mpk.sprite = SPR_GSR2
				mpk.frame = C|FF_TRANS10|FF_FULLBRIGHT
				mpk.blendmode = AST_ADD
				mpk.flags = ($|MF_NOBLOCKMAP|MF_NOCLIPTHING) & ~(MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT)
			end
			mpk.fuse = rng+3
			mpk.momz = ( min(14*s.scale, max(3*s.scale, Rspeed/9)) *P_MobjFlip(s)) -  ((s.z-GS.LastGrindZ)/3)
			P_InstaThrust(mpk, ang+(rng2*ANG1), rng/(2<<16))
			P_Thrust(mpk, ang+(rng2*(ANG1/2)), (GS.grinding==2) and Rspeed*3/2 or Rspeed/5)
			
			mpk.scale = s.scale/6
			if (RENDERER==1) and (d!=1)
				mpk.flags2 = $|MF2_DONTDRAW
			end
		end
	end
end


--SMSreborn's stretch function, edited to fit rails.
RAIL.Stretch = function(s, GS, yu) 
	--Cancel Conditions	
	if not yu.timer
	or (s.spritexscale == FRACUNIT and s.spriteyscale == FRACUNIT and s.spriteyoffset == 0) --We're already done!
	or yu.air and ( P_IsObjectOnGround(s) or (s.eflags & MFE_JUSTHITFLOOR) ) and not (s.momz*P_MobjFlip(s) > 0) --Cancels instantly when we hit a floor.
	or yu.ground and not (P_IsObjectOnGround(s) and s.momz*P_MobjFlip(s) <= 0) --Cancels if we leave the floor instead.	
		s.spritexscale,s.spriteyscale = FRACUNIT,FRACUNIT
		s.spritexoffset,s.spriteyoffset = 0,0
		GS.stretch = nil
		local tails = s.player and s.player.followmobj
		if tails and tails.valid
			tails.spritexoffset,tails.spriteyoffset = s.spritexoffset, s.spriteyoffset
			tails.spritexscale,tails.spriteyscale = s.spritexscale, s.spriteyscale
		end			
		return false --Done!
	end
	if yu.Xspeed == nil yu.Yspeed = 5000 yu.Xspeed = 5000 end  --Default values not set...??

	yu.timer = $-1
	
	if s.spritexscale != FRACUNIT
		s.spritexscale = (s.spritexscale > FRACUNIT) and max($-yu.Xspeed, FRACUNIT) or min($+yu.Xspeed, FRACUNIT)
	end
	if s.spriteyscale != FRACUNIT
		s.spriteyscale = (s.spriteyscale > FRACUNIT) and max($-yu.Yspeed, FRACUNIT) or min($+yu.Yspeed, FRACUNIT)
	end	
	--Bonus yoffset movement?
	if s.spriteyoffset != 0 
		if yu.OFFspeed == nil yu.OFFspeed = FRACUNIT end
		s.spriteyoffset = (s.spriteyoffset > 0) and max($-yu.OFFspeed, 0) or min($+yu.OFFspeed, 0)
		if s.spritexoffset != 0 
			s.spritexoffset = (s.spriteyoffset > 0) and max($-yu.OFFspeed, 0) or min($+yu.OFFspeed, 0)
		end
	end
	local tails = s.player and s.player.followmobj
	if tails and tails.valid
		tails.spritexoffset,tails.spriteyoffset = s.spritexoffset, s.spriteyoffset
		tails.spritexscale,tails.spriteyscale = s.spritexscale, s.spriteyscale
	end	
end

--Special effect for windrails.
RAIL.SpawnRailWind = function(DO, RNG, m, seg, SIDE, SEGSPAWN)
	if (abs(m.GSrailZDelta or 0) > ANG20*2) return end --

	local OFFSET = abs(RNG/800)
	if (DO%(4+OFFSET/6))==0
		local ISIDE = nil
		if (leveltime%3==0)
			ISIDE = (RNG>=0) and 2 or -2
		end
		RAIL.RailWind(SIDE or seg, ISIDE, tonumber(m.GS_windrail), nil, nil, SEGSPAWN)
		if not S_SoundPlaying(seg, sfx_s3kcel)
			S_StartSound(seg, sfx_s3kcel)
		end
	end
	--Windrails can also continuously spawn leaves if specified.
	if type(m.GSinfo and m.GSinfo.spawnleaves)=="number" and (DO%(8+OFFSET))==0 
		if not (leveltime%4==0) and not (leveltime%3==0)
		and not (DO==0 and leveltime%5==0)
			local ghs = RAIL.SpawnLeaves(seg, m.GSinfo.spawnleaves, nil, nil, RNG*180, false)
			local ANG = m.angle+ANGLE_180

			ghs.momx,ghs.momy = $*5/4,$*5/4
			P_Thrust(ghs, ANG,  (abs(RNG)*260) + max(1, (10-(DO/4)))<<16)
			if (RNG < -800)
				ghs.momz = (RNG < -8600) and -$/2 or -$/3
				ghs.eflags = $|MFE_VERTICALFLIP
				ghs.flags2 = $|MF2_OBJECTFLIP
			elseif (RNG > 4400)
				ghs.momz = (RNG > 7600) and $/4 or $/3
			else
				ghs.momz = (RNG > 2800) and $/2 or $*2/3
			end
			if (DO > 5)
				ghs.scale = $*2/3
			end
			if m.GSrailZDelta and (abs(m.GSrailZDelta) > ANG1)
				local TMOMZ = (m.GSrailZDelta/(ANG1/900))<<5
				ghs.momz = $-TMOMZ
			end
			P_MoveOrigin(ghs, ghs.x+FixedMul(RNG*999+(DO<<17), cos(ANG)), ghs.y+FixedMul(RNG*999+(DO<<17), sin(ANG)), ghs.z)
		end
	end
end

--Example of rail variables you can set to a custom object/enemy.
--Feel free to copy-paste and edit them as you see fit.
--If none of these are set, the object will use the default values!
/*
addHook("MobjSpawn", function(s)

	s.GScantgrind = false --This enemy can't grind? DEFAULT = false
	s.GSwillfacerail = 0 --If set to 1, faces rail's angle when grinding. DEFAULT = 0
	s.GSrailminspeed = 4 --Minimum grind speed. Don't apply FRACUNITs here. DEFAULT = 12
	s.GSignoreslopes = false --Ignore the rail's slope physics? DEFAULT = false
	s.GSsilentgrind = 0 -- 0: Sparks+SFX.  1: No SFX or Sparks!  2: SFX but no Sparks! DEFAULT = 0

end, MT_YOURCUSTOMOBJECTHERE)
*/

/*spriteinfo[SPR_GSRL].pivot["B"] = {x = 90, y = 1}
spriteinfo[SPR_GSRL].pivot["C"] = {x = 90, y = 1}
spriteinfo[SPR_GSRL].pivot["BL"] = {x = 90, y = 1}
spriteinfo[SPR_GSRL].pivot["BR"] = {x = 90, y = 1}
spriteinfo[SPR_GSRL].pivot["UHHFUCKPLSWORK"] = {x = 64, y = 0}


print("\x82 ROTAXIS = "+spriteinfo[SPR_GSRL].pivot["B"].rotaxis)
print("\x84 \n Pivot \n frame \n B: \n "+spriteinfo[SPR_GSRL].pivot["B"].x+" and Y = "+spriteinfo[SPR_GSRL].pivot["B"].y)*/



--Note to self: You type out (p.cmd.angleturn<<16) like so.
--That will basically reflect the same angle as the camera facing the player's back. 
--Make angle comparisons to see if player is pressing left/right, without using FORCESTRAFE.




